﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.Text;
using WCFServiceWebRole1.DataObjects;
using System.ServiceModel.Web;
using System.Web;
using System.ServiceModel.Activation;
using DataAccessLayer;
using GlobalObjects;
using System.Diagnostics;
using System.ServiceModel.Channels;
using System.Globalization;
using System.Threading.Tasks;

namespace WCFServiceWebRole1.Services
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "device" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, please select device.svc or device.svc.cs at the Solution Explorer and start debugging.


    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class device : Idevice
    {
        public static Uri servicePath = new Uri("http://uclwebservicetest.cloudapp.net/Services/device.svc");
        // public static Uri servicePath = new Uri(baseUri, "Services/device.svc");


        public static DeviceEntity deviceEntity = new DeviceEntity();
        public static SensorEntity sensorEntity = new SensorEntity();
        public static SensorAccessTimeEntity monitorSensorEntity = new SensorAccessTimeEntity();
        //  DAL dal = new DAL();


        #region Devices

        public string deviceregister(DeviceRegistrationRequestDataContract rawJSON)
        {
            OutgoingWebResponseContext outResponse = WebOperationContext.Current.OutgoingResponse;
            List<Sensor> existingSensors;
            //Check if the device id is not null
            if (rawJSON.DeviceId == 0)
            {
                //throw new ArgumentNullException("DeviceId", "Device Id is not present");
                ErrorData errordata = new ErrorData("Device Id is not present");
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.NoContent);
            }

            //Check if all the fields in the sensor is not null
            foreach (var sensor in rawJSON.Sensors)
            {
                if (sensor.Name == null || sensor.Name == "" || sensor.Type == "" || sensor.Type == null || sensor.Make == null || sensor.Make == "")
                {
                    throw new ArgumentNullException("Sensor", "Mandatory fields are missing in sensor details");
                }
            }

            //Check if the device id exist
            if (!deviceEntity.idExist_DAL(rawJSON.DeviceId))
            {
                throw new KeyNotFoundException("Device Id does not exist");
            }

            //check if the api key is the right one for the device
            OperationContext currentContext = OperationContext.Current;
            HttpRequestMessageProperty reqMsg = currentContext.IncomingMessageProperties["httpRequest"] as HttpRequestMessageProperty;
            string authToken = reqMsg.Headers["Authorization"];
            string key = "";
            string[] data = authToken.Split(' ');
            if (data.Length == 2 && data.ElementAt<string>(0) == "APIKey")
            {
                key = data.ElementAt<string>(1);
            }

            //given a device id and apikey, check if they are correct
            if (!deviceEntity.APIKeyAndDeviceIdMatch_DAL(key, rawJSON.DeviceId))
            {
                ErrorData errordata = new ErrorData("Apikey and device id does not match");
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.NoContent);
                //                throw new ArgumentException("Apikey and device id does not match");
            }

            //Get the ipaddress of the device from the http request
            OperationContext context = OperationContext.Current;
            MessageProperties messageProperties = context.IncomingMessageProperties;
            RemoteEndpointMessageProperty endpointProperty =
              messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
            string ipaddress = endpointProperty.Address.ToString();

            //Store the ip address of the device in the repository            
            try
            {
                deviceEntity.addIpAddress_DAL(rawJSON.DeviceId, ipaddress);
            }
            catch (Exception ex)
            {
                ErrorData errordata = new ErrorData("addIpAddress_DAL Failed" + ex.Message);
                errordata.StackTrace = ex.StackTrace;
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.NoContent);
                //throw ex;
            }

            //add the confirmation details in device repo
            try
            {
                Device devicesample = new Device()
                {
                    Id = rawJSON.DeviceId,
                    Confirmation_date = DateTime.UtcNow,
                    Confirmed = true
                };

                deviceEntity.updateDeviceDetails_DAL(devicesample);
            }
            catch (Exception ex)
            {
                ErrorData errordata = new ErrorData("updateDeviceDetails_DAL Failed" + ex.Message);
                errordata.StackTrace = ex.StackTrace;
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.NoContent);


                //outResponse.StatusCode = HttpStatusCode.BadRequest;
                //throw new Exception("Updating Device Details Failed", ex);
            }
            try
            {
                existingSensors = sensorEntity.getSensorsByDeviceId_DAL(rawJSON.DeviceId);
            }
            catch (Exception ex)
            {
                ErrorData errordata = new ErrorData("getSensorsByDeviceId_DAL Failed" + ex.Message);
                errordata.StackTrace = ex.StackTrace;
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.NoContent);

                //outResponse.StatusCode = HttpStatusCode.BadRequest;
                //throw new Exception("Sensor Addition Failed.", ex);
            }

            SensorAccessTimeEntity sensorAccessTimeEntity = new SensorAccessTimeEntity();
            //Add the sensor details, return sensor ids
            foreach (var sensor in rawJSON.Sensors)
            {
                if (existingSensors.Count != 0)
                {
                    bool found = false;
                    foreach (var existingSensor in existingSensors)
                    {
                        if (existingSensor.Name == sensor.Name)
                        {
                            try
                            {
                                if (existingSensor.Deviceid == rawJSON.DeviceId)
                                {
                                    monitorSensorEntity.insertOrUpdateAcceessTime_DAL(existingSensor.Id, DateTime.UtcNow.ToString());
                                    monitorSensorEntity.setFaultTolerance_DAL(existingSensor.Id, sensor.Fault_tolerance);
                                    monitorSensorEntity.setIp_DAL(existingSensor.Id, ipaddress);
                                    sensorEntity.setSensorStatus_DAL(existingSensor.Id, true);
                                    outResponse.Headers.Add(existingSensor.Name, existingSensor.Id.ToString());
                                    found = true;
                                }
                            }
                            catch (Exception ex)
                            {
                                ErrorData errordata = new ErrorData("Existing Sensor Failed.Updating Sensor access time table." + ex.Message);
                                errordata.StackTrace = ex.StackTrace;
                                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.NoContent);
                            }
                        }
                    }
                    if (!found)
                    {
                        Sensor newsensor = new Sensor()
                        {
                            Deviceid = rawJSON.DeviceId,
                            Interval_measure = sensor.Interval_measure,
                            Interval_send = sensor.Interval_send,
                            Make = sensor.Make,
                            Metadata = sensor.Metadata,
                            Name = sensor.Name,
                            Type = sensor.Type,
                            Fault_tolerance = sensor.Fault_tolerance
                        };
                        try
                        {
                            Sensor response_sensor = sensorEntity.addSensors_DAL(newsensor);
                            monitorSensorEntity.insertOrUpdateAcceessTime_DAL(response_sensor.Id, DateTime.UtcNow.ToString());
                            monitorSensorEntity.setFaultTolerance_DAL(response_sensor.Id, newsensor.Fault_tolerance);
                            monitorSensorEntity.setIp_DAL(response_sensor.Id, ipaddress);
                            sensorEntity.setSensorStatus_DAL(response_sensor.Id, true);
                            outResponse.Headers.Add(response_sensor.Name, response_sensor.Id.ToString());
                        }
                        catch (Exception ex)
                        {
                            ErrorData errordata = new ErrorData("Sensor Addition Failed" + ex.Message);
                            errordata.StackTrace = ex.StackTrace;
                            throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.NoContent);
                            //outResponse.StatusCode = HttpStatusCode.BadRequest;
                            //throw new Exception("Sensor Addition Failed.", ex);
                        }

                    }
                }
                else
                {
                    Sensor newsensor = new Sensor()
                    {
                        Deviceid = rawJSON.DeviceId,
                        Interval_measure = sensor.Interval_measure,
                        Interval_send = sensor.Interval_send,
                        Make = sensor.Make,
                        Metadata = sensor.Metadata,
                        Name = sensor.Name,
                        Type = sensor.Type,
                        Fault_tolerance = sensor.Fault_tolerance
                    };
                    try
                    {
                        Sensor response_sensor = sensorEntity.addSensors_DAL(newsensor);
                        monitorSensorEntity.insertOrUpdateAcceessTime_DAL(response_sensor.Id, DateTime.UtcNow.ToString());
                        monitorSensorEntity.setFaultTolerance_DAL(response_sensor.Id, newsensor.Fault_tolerance);
                        monitorSensorEntity.setIp_DAL(response_sensor.Id, ipaddress);
                        sensorEntity.setSensorStatus_DAL(response_sensor.Id, true);
                        outResponse.Headers.Add(response_sensor.Name, response_sensor.Id.ToString());
                    }
                    catch (Exception ex)
                    {
                        ErrorData errordata = new ErrorData("Sensor Addition Failed" + ex.Message);
                        errordata.StackTrace = ex.StackTrace;
                        throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.NoContent);
                        //outResponse.StatusCode = HttpStatusCode.BadRequest;
                        //throw new Exception("Sensor Addition Failed.", ex);
                    }
                }
            }
            //return the success message
            return "Success";
        }
        public string recordHearBeat(int deviceid)
        {
            try
            {
                DateTime now = DateTime.UtcNow;

                //Get the ipaddress of the device from the http request
                OperationContext context = OperationContext.Current;
                MessageProperties messageProperties = context.IncomingMessageProperties;
                RemoteEndpointMessageProperty endpointProperty =
                  messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
                string ipaddress = endpointProperty.Address.ToString();

                Task.Factory.StartNew(() => this.updateHeartBeatAccessTimes(ipaddress, deviceid,now));

            }
            catch (Exception ex)
            {
                throw new ArgumentNullException("Operation Failed." + ex.Message + ex.StackTrace);
            }
            return "Success";
        }
        private void updateHeartBeatAccessTimes(string ipaddress, int deviceid, DateTime now)
        {
            foreach (var sensor in sensorEntity.getSensorsByDeviceId_DAL(deviceid))
            {

                sensorEntity.setSensorStatus_DAL(sensor.Id, true);
                monitorSensorEntity.insertOrUpdateAcceessTime_DAL(sensor.Id, now.ToString());
                monitorSensorEntity.setIp_DAL(sensor.Id, ipaddress);
            }

        }

        public string changeIpAddress(int deviceid, string ipaddress)
        {
            //Check if the device id and sensor id exists
            if (!deviceEntity.idExist_DAL(deviceid))
            {
                ErrorData errordata = new ErrorData("Given device id does not exist.");
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.BadRequest);

            }

            //Change the ip address of the device
            try
            {
                OperationContext context = OperationContext.Current;
                MessageProperties prop = context.IncomingMessageProperties;
                RemoteEndpointMessageProperty endpoint =
                    prop[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
                string ip = endpoint.Address;

                deviceEntity.addIpAddress_DAL(deviceid, ip);
            }
            catch (Exception ex)
            {
                ErrorData errordata = new ErrorData("Adding IP failed." + ex.Message);
                errordata.StackTrace = ex.StackTrace;
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.BadRequest);
            }
            //Check if the apikey is for this device
            //return a response message
            return "Success";
        }

   
        #endregion

        #region Sensors

        public string addSensor(Sensor sensor)
        {
            OutgoingWebResponseContext outResponse = WebOperationContext.Current.OutgoingResponse;

            //Check if the device id is null
            if (sensor.Deviceid == 0)
            {
                throw new ArgumentNullException("DeviceId cannot be null");
            }

            //Check if the device id exists
            if (!deviceEntity.idExist_DAL(sensor.Deviceid))
            {
                throw new ArgumentNullException("DeviceId does not exist");
            }
            //Check if the apikey corresponds to the given apikey

            //check if the mandatory fields of sensor are null
            if (sensor.Name == null || sensor.Type == null || sensor.Make == null || sensor.Interval_measure == null || sensor.Interval_send == null ||
                sensor.Name == "" || sensor.Type == "" || sensor.Make == "" || sensor.Interval_measure == "" || sensor.Interval_send == "")
            {
                throw new ArgumentNullException("Mandatory fields missing");
            }
            //Get the ipaddress of the device from the http request
            OperationContext context = OperationContext.Current;
            MessageProperties messageProperties = context.IncomingMessageProperties;
            RemoteEndpointMessageProperty endpointProperty =
              messageProperties[RemoteEndpointMessageProperty.Name] as RemoteEndpointMessageProperty;
            string ipaddress = endpointProperty.Address.ToString();

            //Check if the sensor name is unique
            if (!sensorEntity.sensorNameExist_DAL(sensor.Name))
            {
                //add the sensor details
                try
                {
                    Sensor createdSensor = sensorEntity.addSensors_DAL(sensor);
                    monitorSensorEntity.insertOrUpdateAcceessTime_DAL(createdSensor.Id, DateTime.UtcNow.ToString());
                    monitorSensorEntity.setIp_DAL(createdSensor.Id, ipaddress);
                    return createdSensor.Id.ToString();
                }
                catch (Exception ex)
                {
                    throw ex;
                }
            }
            //create a response message in the header
            //outResponse.Headers.Add(response_sensor.Name, response_sensor.Id.ToString());
            //return the response message
            return null;
        }

        public string updateSensor(string isensorid, Sensor sensor)
        {
            OutgoingWebResponseContext outResponse = WebOperationContext.Current.OutgoingResponse;
            Sensor item;
            // type casting userid to INT
            int sensorid = -1;
            bool res = Int32.TryParse(isensorid, out sensorid);
            if (!res)
            {
                ErrorData errordata = new ErrorData("Given Sensor Id is not an integer");
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.BadRequest);
            }

            //Checking if the userid in the url matches with the user id the body
            if (sensorid != sensor.Id)
            {
                ErrorData errordata = new ErrorData("Sensor Id in the request does not match with the resource being accessed.");
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.BadRequest);
            }
            //Checking if the user id is empty 
            if (sensor.Id == 0)
            {
                ErrorData errordata = new ErrorData("Sensor ID in the Request Body cannot be null or Zero");
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.BadRequest);
            }
            if (sensor.Deviceid == 0)
            {
                ErrorData errordata = new ErrorData("Device ID in the Request Body cannot be null or Zero");
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.BadRequest);
            }


            //Making empty field to null value
            #region mapping
            if (sensor.Interval_measure == null || sensor.Interval_measure == "")
            {
                sensor.Interval_measure = null;
            }
            if (sensor.Interval_send == null || sensor.Interval_send == "")
            {
                sensor.Interval_send = null;
            }
            if (sensor.Make == null || sensor.Make == "")
            {
                sensor.Make = null;
            }
            if (sensor.Metadata == null || sensor.Metadata == "")
            {
                sensor.Metadata = null;
            }
            if (sensor.Name == null || sensor.Name == "")
            {
                sensor.Name = null;
            }
            else
            {
                if (sensorEntity.sensorNameExist_DAL(sensor.Name))
                {
                    ErrorData errordata = new ErrorData("Sensor Name already exists.Cannot be changed");
                    throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.BadRequest);
                }
            }
            if (sensor.Type == null || sensor.Type == "")
            {
                sensor.Type = null;
            }
            #endregion

            //Update the details in the repository 
            try
            {
                item = sensorEntity.updateSensorDetails_DAL(sensor);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            //Creating response message
            outResponse.StatusCode = HttpStatusCode.Accepted;
            Uri location = new Uri(servicePath, "sensor/" + item.Id.ToString());

            //Returning the response
            return "Success";

        }

        public SensorResponse getSensorsById(string isensorid)
        {

            OutgoingWebResponseContext outResponse = WebOperationContext.Current.OutgoingResponse;
            Sensor sensor;

            // type casting sensorid to INT
            int sensorid = -1;
            bool res = Int32.TryParse(isensorid, out sensorid);
            if (!res)
            {
                outResponse.StatusCode = HttpStatusCode.BadRequest;
                outResponse.StatusDescription = "Not an Integer";
                return null;
            }

            //Retriving the sensor details
            try
            {
                sensor = sensorEntity.getSensorById_DAL(sensorid);
            }
            catch (Exception ex)
            {
                ErrorData errordata = new ErrorData("Retrieval Failed." + ex.Message);
                throw new WebFaultException<ErrorData>(errordata, HttpStatusCode.BadRequest);
            }

            //Response Message
            outResponse.StatusCode = HttpStatusCode.OK;
            Uri location = new Uri(servicePath, "sensor/" + sensor.Id);

            return new SensorResponse()
            {
                Resource = location,
                SensorDetails = sensor

            };

        }

        public bool deleteSensor(string deviceid, string sensorid)
        {
            return true;
        }

        public string measureInterval(int deviceid, int sensorid, int interval)
        {
            //Find the ip address of the device

            //Put the interval change time inside a queue

            //Send the interval time to the device

            //

            return "Success";
        }

        #endregion














    }
}

